#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##

from datetime import timedelta
from persistance import Persistance


class Statistics(Persistance):
    """
    Persistant object to record game statistics for completed games. Subclasses
    Persistance.Persistance(). Contains the additional value __session to hold
    games from this session that is not maintained between sessions.
    """
    _empty_value = []
    __session = {}

    def basic(self, player, gameid, all_=True):
        """
        basic(self, mode:str, player:str, gameid:int, all_=True:bool)
            => (won:int, lost:int)

        Show win and losses for game with gameid played by player for all_
        previous or just this sesion.
        """
        win, loss = 0, 0
        for game in self.games(gameid, all_):
            if game['player'] == player:
                win += 1 if game['state'] in [1, 2] else 0
                loss += 1 if game['state'] == 0 else 0
        return win, loss

    def full(self, player, gameid, all_=True):
        """
        full(self, player:str, gameid:int, all_=True)
            => (won:int, lost:int, time:obj, moves:int)

        Show win and losses along with average time and moves for game with
        gameid played by player for all_ previous or just this sesion.
        """
        time = self.results(player, gameid, 'time', 'avg', all_)
        moves = self.results(player, gameid, 'moves', 'avg', all_)
        win, loss = self.basic(player, gameid, all_)
        return win, loss, time, moves

    def compare(self, player, gameid1, gameid2, all_=True):
        """
        compare(self, player:str, gameid1:int, gameid2:int)
            => full(player, gameid1, all_) + full(player, gameid2, all_)

        Show self.full return values for gameid1 and gameid2 played by player
        for all_ previous or just this sesion to perform comparisons.
        """
        return (self.full(player, gameid1, all_) +
                self.full(player, gameid2, all_))

    def players(self):
        """
        players() > list

        Show full unique list of players from all sessions.
        """
        results = []
        for id_, games in iter(self):
            if type(games) is list:
                players = {g['player'] for g in games}
            results.extend(list(players))
        return list({player for player in results if player is not None})

    def games(self, gameid, all_=True):
        """
        games(self, gameid:[int|'all'], all_=True) => list(dict)

        Merge game statistics from mutilple or single gameids into single list.
        """
        if gameid == 'all':
            results = []
            for id_, games in (iter(self) if all_ else self.__session.items()):
                if type(games) is list:
                    results.extend(games)
        else:
            results = self[gameid] if all_ else self.__session.get(gameid, [])
        return results

    def results(self, player, gameid, name, mode='', all_=True):
        """
        results(self, player:str, gameid:int, name:str, mode='', all_=True) => *

        Show value for name as processed by mode for game of gameid played by
        player for all_ previous or just this sesion to perform comparisons.

        Value of name:
            - [score | moves] returns float for avg and int for other modes.
            - [time] returns a timedelta object for all modes.

        If mode is not used or is not [avg | min | max | num | sum] a list of
        name values are returned.
        """
        game_results = [game[name] for game in self.games(gameid, all_)
                        if game['player'] == player]
        assert type(game_results) == list
        add = lambda x, y: x + y
        if game_results:
            game_results.sort()
            func = {'avg': lambda x: (
                            reduce(add, x)/len(x) if reduce(add, x) else 0),
                    'min': lambda x: min(x),
                    'max': lambda x: max(x),
                    'num': lambda x: len(x),
                    'sum': lambda x: reduce(add, x),
                    }.get(mode.lower(), lambda x: x)

            return func(game_results)

        return timedelta() if name.lower() == 'time' else 0

    def summary(self, player, gameid, all_=True):
        """
        summary(self, player:str, gameid:int, all_=True) => dict(list)
        """
        section = {}
        for label, name in [(_('Time'), 'time'), (_('Moves'), 'moves'),
                            (_('Score'), 'score')]:
            section[label] = []
            for mode in ['min', 'max', 'avg', 'sum']:
                section[label].append(self.results(player, gameid, name, mode, all_))
            section[label].append(self.results(player, gameid, name, all_=all_))
        return section

    def __appendlogs(self, **log):
        """
        __appendlogs(self, **log) => None
        """
        self[log['gid']] += [log]
        self.__session.setdefault(log['gid'], [])
        self.__session[log['gid']] += [log]

    def update(self, player, game, status):
        """
        __update(self, player:str, game:obj, status:int) => tuple(int, int, int)

        append a log entry for current game.
        """
        if status in [0, 1, 2]:
            assert isinstance(game._timer.elapsed, timedelta)
            self.__appendlogs(gid=game['id'], player=player,
                              number=int(repr(game.random)),
                              state=status, sdate=game._timer.started,
                              score=game.score, time=game._timer.elapsed,
                              moves=game.move_total)

    def position(self, player, game):
        """
        position(self, player:str, game:int) => tuple(int, int, int)

        Return the position in self.__attributes for time, moves, and score.
        """
        def by(name, value, gameid):
            result = [game[name] for game in self[gameid]
                      if game['player'] == player]
            result.sort()
            return result.index(value)

        return ([by('time', game._timer.elapsed, game['id'])] +
               [by('moves', game.move_total, game['id'])] +
               [by('score', game.score(), game['id'])])

    def position_text(self, player, game):
        time_, move_, score_ = self.position(player, game)
        rank = []
        if time_:
            rank += [_('#{} for best times').format(time_)]
        if move_:
            rank += [_('#{} for best moves').format(move_)]
        #if score_:
        #    rank += [_('#{} for best scores').format(move_)]
        if len(rank) > 1:
            rank = ' and '.join([', '.join(rank[:-1]), rank[-1:]])
        else:
            rank = rank[0]
            return _('Your ranking is {}.').format(rank)

    def reset(self, player, game_id=None):
        if game_id is not None and isinstance(self[index], list):
            self[index] = [g for g in self[index] if g['player'] != player]
        else:
            for id_, games in (g for i, g in self if isinstance(games, list)):
                games = [g for g in games if g['player'] != player]

STAT_DB = Statistics()